<template>
  <transition :name="clsPrefix + 'zoom-in-top'" @after-leave="$emit('dodestroy')">
    <div
      v-show="visible"
      :class="wrapClasses">
      <div :class="[pickerPanelCls + '__body-wrapper']">
        <slot name="sidebar" :class="[pickerPanelCls + '__sidebar']"></slot>
        <div :class="[pickerPanelCls + '__sidebar']" v-if="shortcuts">
          <button
            type="button"
            :class="[pickerPanelCls + '__shortcut']"
            v-for="(shortcut, key) in shortcuts"
            :key="key"
            @click="handleShortcutClick(shortcut)">{{shortcut.text}}</button>
        </div>
        <div :class="[pickerPanelCls + '__body']">
          <div :class="[dateRangePickerCls + '__time-header']" v-if="showTime">
            <span :class="[dateRangePickerCls + '__editors-wrap']">
              <span :class="[dateRangePickerCls + '__time-picker-wrap']">
                <d-input
                  size="small"
                  :disabled="rangeState.selecting"
                  ref="minInput"
                  :placeholder="t(localePrefix + 'startDate')"
                  :class="[dateRangePickerCls + '__editor']"
                  :value="minVisibleDate"
                  @input.native="handleDateInput($event, 'min')"
                  @change.native="handleDateChange($event, 'min')" />
              </span>
              <span :class="[dateRangePickerCls + '__time-picker-wrap']" v-click-outside="handleMinTimeClose">
                <d-input
                  size="small"
                  :disabled="rangeState.selecting"
                  :placeholder="t(localePrefix + 'startTime')"
                  :class="[dateRangePickerCls + '__editor']"
                  :value="minVisibleTime"
                  @on-focus="minTimePickerVisible = true"
                  @change.native="handleTimeChange($event, 'min')" />
                <time-picker
                  ref="minTimePicker"
                  @pick="handleMinTimePick"
                  :time-arrow-control="arrowControl"
                  :visible="minTimePickerVisible"
                  @mounted="$refs.minTimePicker.format=timeFormat">
                </time-picker>
              </span>
            </span>
            <Icon name="chevron-right"></Icon>
            <span :class="[dateRangePickerCls + '__editors-wrap', 'is-right']">
              <span :class="[dateRangePickerCls + '__time-picker-wrap']">
                <d-input
                  size="small"
                  :disabled="rangeState.selecting"
                  :placeholder="t(localePrefix + 'endDate')"
                  :class="[dateRangePickerCls + '__editor']"
                  :value="maxVisibleDate"
                  :readonly="!minDate"
                  @input.native="handleDateInput($event, 'max')"
                  @change.native="handleDateChange($event, 'max')" />
              </span>
              <span :class="[dateRangePickerCls + '__time-picker-wrap']" v-click-outside="handleMaxTimeClose">
                <d-input
                  size="small"
                  :disabled="rangeState.selecting"
                  ref="maxInput"
                  :placeholder="t(localePrefix + 'endTime')"
                  :class="[dateRangePickerCls + '__editor']"
                  :value="maxVisibleTime"
                  @on-focus="minDate && (maxTimePickerVisible = true)"
                  :readonly="!minDate"
                  @change.native="handleTimeChange($event, 'max')" />
                <time-picker
                  ref="maxTimePicker"
                  @pick="handleMaxTimePick"
                  :time-arrow-control="arrowControl"
                  :visible="maxTimePickerVisible"
                  @mounted="$refs.maxTimePicker.format=timeFormat">
                </time-picker>
              </span>
            </span>
          </div>
          <div :class="[pickerPanelCls + '__content', dateRangePickerCls + '__content', 'is-left']">
            <div :class="[dateRangePickerCls + '__header']">
              <button
                type="button"
                @click="leftPrevYear"
                :class="[pickerPanelCls + '__icon-btn', clsPrefix + 'd-arrow-left']">
                <Icon name="chevrons-left"></Icon>
              </button>
              <button
                type="button"
                @click="leftPrevMonth"
                :class="[pickerPanelCls + '__icon-btn', clsPrefix + 'arrow-left']">
                <Icon name="chevron-left"></Icon>
              </button>
              <button
                type="button"
                @click="leftNextYear"
                v-if="unlinkPanels"
                :disabled="!enableYearArrow"
                :class="[{ 'is-disabled': !enableYearArrow }, pickerPanelCls + '__icon-btn', clsPrefix + 'd-arrow-right']">
                <Icon name="chevrons-right"></Icon>
              </button>
              <button
                type="button"
                @click="leftNextMonth"
                v-if="unlinkPanels"
                :disabled="!enableMonthArrow"
                :class="[{ 'is-disabled': !enableMonthArrow }, pickerPanelCls + '__icon-btn', clsPrefix + 'arrow-right']">
                <Icon name="chevron-right"></Icon>
              </button>
              <div>{{ leftLabel }}</div>
            </div>
            <date-table
              selection-mode="range"
              :date="leftDate"
              :default-value="defaultValue"
              :min-date="minDate"
              :max-date="maxDate"
              :range-state="rangeState"
              :disabled-date="disabledDate"
              @changerange="handleChangeRange"
              :first-day-of-week="firstDayOfWeek"
              @pick="handleRangePick">
            </date-table>
          </div>
          <div :class="[pickerPanelCls + '__content', dateRangePickerCls + '__content', 'is-right']">
            <div :class="[dateRangePickerCls + '__header']">
              <button
                type="button"
                @click="rightPrevYear"
                v-if="unlinkPanels"
                :disabled="!enableYearArrow"
                :class="[{ 'is-disabled': !enableYearArrow }, pickerPanelCls + '__icon-btn', clsPrefix + 'd-arrow-left']">
                <Icon name="chevrons-left"></Icon>
              </button>
              <button
                type="button"
                @click="rightPrevMonth"
                v-if="unlinkPanels"
                :disabled="!enableMonthArrow"
                :class="[{ 'is-disabled': !enableMonthArrow }, pickerPanelCls + '__icon-btn', clsPrefix + 'arrow-left']">
                <Icon name="chevron-left"></Icon>
              </button>
              <button
                type="button"
                @click="rightNextYear"
                :class="[pickerPanelCls + '__icon-btn', clsPrefix + 'd-arrow-right']">
                <Icon name="chevrons-right"></Icon>
              </button>
              <button
                type="button"
                @click="rightNextMonth"
                :class="[pickerPanelCls + '__icon-btn', clsPrefix + 'arrow-right']">
                <Icon name="chevron-right"></Icon>
              </button>
              <div>{{ rightLabel }}</div>
            </div>
            <date-table
              selection-mode="range"
              :date="rightDate"
              :default-value="defaultValue"
              :min-date="minDate"
              :max-date="maxDate"
              :range-state="rangeState"
              :disabled-date="disabledDate"
              @changerange="handleChangeRange"
              :first-day-of-week="firstDayOfWeek"
              @pick="handleRangePick">
            </date-table>
          </div>
        </div>
      </div>
      <div :class="[pickerPanelCls + '__footer']" v-if="showTime">
        <d-button
          size="small"
          type="borderless"
          :class="[pickerPanelCls + '__link-btn']"
          @click="handleClear">
          {{ t(localePrefix + 'clear') }}
        </d-button>
        <d-button
          size="small"
          type="primary"
          :class="[pickerPanelCls + '__link-btn']"
          :disabled="btnDisabled"
          @click="handleConfirm(false)">
          {{ t(localePrefix + 'confirm') }}
        </d-button>
      </div>
    </div>
  </transition>
</template>

<script type="text/babel">
import Config from '../../../config'
import {
  formatDate,
  parseDate,
  isDate,
  modifyDate,
  modifyTime,
  modifyWithTimeString,
  prevYear,
  nextYear,
  prevMonth,
  nextMonth,
  extractDateFormat,
  extractTimeFormat
} from '../util'
import {directive as ClickOutside} from 'v-click-outside-x'
import Locale from '../../../mixins/locale'
import TimePicker from './time'
import DateTable from '../basic/date-table'
import Icon from '../../icon'
import DInput from '../../input/input.vue'
import DButton from '../../button/button.vue'

const clsPrefix = Config.clsPrefix
const localePrefix = `${Config.localePrefix}.datepicker.`

const advanceDate = (date, amount) => {
  return new Date(new Date(date).getTime() + amount)
}

const calcDefaultValue = (defaultValue) => {
  if (Array.isArray(defaultValue)) {
    return [new Date(defaultValue[0]), new Date(defaultValue[1])]
  } else if (defaultValue) {
    return [new Date(defaultValue), advanceDate(defaultValue, 24 * 60 * 60 * 1000)]
  } else {
    return [new Date(), advanceDate(Date.now(), 24 * 60 * 60 * 1000)]
  }
}

export default {
  components: { TimePicker, DateTable, Icon, DInput, DButton },

  mixins: [Locale],

  directives: { ClickOutside },

  data () {
    return {
      clsPrefix: clsPrefix,
      pickerPanelCls: `${clsPrefix}picker-panel`,
      dateRangePickerCls: `${clsPrefix}date-range-picker`,
      localePrefix: localePrefix,
      popperClass: '',
      value: [],
      defaultValue: null,
      defaultTime: null,
      minDate: '',
      maxDate: '',
      leftDate: new Date(),
      rightDate: nextMonth(new Date()),
      rangeState: {
        endDate: null,
        selecting: false,
        row: null,
        column: null
      },
      showTime: false,
      shortcuts: '',
      visible: '',
      disabledDate: '',
      firstDayOfWeek: 7,
      minTimePickerVisible: false,
      maxTimePickerVisible: false,
      format: '',
      arrowControl: false,
      unlinkPanels: false
    }
  },

  computed: {
    wrapClasses () {
      return [
        this.pickerPanelCls,
        this.dateRangePickerCls,
        `${clsPrefix}popper`,
        {
          'has-sidebar': this.$slots.sidebar || this.shortcuts,
          'has-time': !!this.showTime
        },
        this.popperClass
      ]
    },

    btnDisabled () {
      return !(this.minDate && this.maxDate && !this.selecting && this.isValidValue([this.minDate, this.maxDate]))
    },

    leftLabel () {
      return this.leftDate.getFullYear() + ' ' + this.t(localePrefix + 'year') + ' ' + this.t(`${localePrefix}month${this.leftDate.getMonth() + 1}`)
    },

    rightLabel () {
      return this.rightDate.getFullYear() + ' ' + this.t(localePrefix + 'year') + ' ' + this.t(`${localePrefix}month${this.rightDate.getMonth() + 1}`)
    },

    leftYear () {
      return this.leftDate.getFullYear()
    },

    leftMonth () {
      return this.leftDate.getMonth()
    },

    leftMonthDate () {
      return this.leftDate.getDate()
    },

    rightYear () {
      return this.rightDate.getFullYear()
    },

    rightMonth () {
      return this.rightDate.getMonth()
    },

    rightMonthDate () {
      return this.rightDate.getDate()
    },

    minVisibleDate () {
      return this.minDate ? formatDate(this.minDate, this.dateFormat) : ''
    },

    maxVisibleDate () {
      return (this.maxDate || this.minDate) ? formatDate(this.maxDate || this.minDate, this.dateFormat) : ''
    },

    minVisibleTime () {
      return this.minDate ? formatDate(this.minDate, this.timeFormat) : ''
    },

    maxVisibleTime () {
      return (this.maxDate || this.minDate) ? formatDate(this.maxDate || this.minDate, this.timeFormat) : ''
    },

    timeFormat () {
      if (this.format) {
        return extractTimeFormat(this.format)
      } else {
        return 'HH:mm:ss'
      }
    },

    dateFormat () {
      if (this.format) {
        return extractDateFormat(this.format)
      } else {
        return 'yyyy-MM-dd'
      }
    },

    enableMonthArrow () {
      const nextMonth = (this.leftMonth + 1) % 12
      const yearOffset = this.leftMonth + 1 >= 12 ? 1 : 0
      return this.unlinkPanels && new Date(this.leftYear + yearOffset, nextMonth) < new Date(this.rightYear, this.rightMonth)
    },

    enableYearArrow () {
      return this.unlinkPanels && this.rightYear * 12 + this.rightMonth - (this.leftYear * 12 + this.leftMonth + 1) >= 12
    }
  },

  watch: {
    minDate (val) {
      this.$nextTick(() => {
        if (this.$refs.maxTimePicker && this.maxDate && this.maxDate < this.minDate) {
          const format = 'HH:mm:ss'
          this.$refs.maxTimePicker.selectableRange = [
            [
              parseDate(formatDate(this.minDate, format), format),
              parseDate('23:59:59', format)
            ]
          ]
        }
      })
      if (val && this.$refs.minTimePicker) {
        this.$refs.minTimePicker.date = val
        this.$refs.minTimePicker.value = val
      }
    },

    maxDate (val) {
      if (val && this.$refs.maxTimePicker) {
        this.$refs.maxTimePicker.date = val
        this.$refs.maxTimePicker.value = val
      }
    },

    minTimePickerVisible (val) {
      if (val) {
        this.$nextTick(() => {
          this.$refs.minTimePicker.date = this.minDate
          this.$refs.minTimePicker.value = this.minDate
          this.$refs.minTimePicker.adjustSpinners()
        })
      }
    },

    maxTimePickerVisible (val) {
      if (val) {
        this.$nextTick(() => {
          this.$refs.maxTimePicker.date = this.maxDate
          this.$refs.maxTimePicker.value = this.maxDate
          this.$refs.maxTimePicker.adjustSpinners()
        })
      }
    },

    value (newVal) {
      if (!newVal) {
        this.minDate = null
        this.maxDate = null
      } else if (Array.isArray(newVal)) {
        this.minDate = isDate(newVal[0]) ? new Date(newVal[0]) : null
        this.maxDate = isDate(newVal[1]) ? new Date(newVal[1]) : null
        if (this.minDate) {
          this.leftDate = this.minDate
          if (this.unlinkPanels && this.maxDate) {
            const minDateYear = this.minDate.getFullYear()
            const minDateMonth = this.minDate.getMonth()
            const maxDateYear = this.maxDate.getFullYear()
            const maxDateMonth = this.maxDate.getMonth()
            this.rightDate = minDateYear === maxDateYear && minDateMonth === maxDateMonth
              ? nextMonth(this.maxDate)
              : this.maxDate
          } else {
            this.rightDate = nextMonth(this.leftDate)
          }
        } else {
          this.leftDate = calcDefaultValue(this.defaultValue)[0]
          this.rightDate = nextMonth(this.leftDate)
        }
      }
    },

    defaultValue (val) {
      if (!Array.isArray(this.value)) {
        const [left, right] = calcDefaultValue(val)
        this.leftDate = left
        this.rightDate = val && val[1] && this.unlinkPanels
          ? right
          : nextMonth(this.leftDate)
      }
    }
  },

  methods: {
    handleClear () {
      this.minDate = null
      this.maxDate = null
      this.leftDate = calcDefaultValue(this.defaultValue)[0]
      this.rightDate = nextMonth(this.leftDate)
      this.$emit('pick', null)
    },

    handleChangeRange (val) {
      this.minDate = val.minDate
      this.maxDate = val.maxDate
      this.rangeState = val.rangeState
    },

    handleDateInput (event, type) {
      const value = event.target.value
      if (value.length !== this.dateFormat.length) return
      const parsedValue = parseDate(value, this.dateFormat)

      if (parsedValue) {
        if (typeof this.disabledDate === 'function' &&
          this.disabledDate(new Date(parsedValue))) {
          return
        }
        if (type === 'min') {
          this.minDate = new Date(parsedValue)
          this.leftDate = new Date(parsedValue)
          this.rightDate = nextMonth(this.leftDate)
        } else {
          this.maxDate = new Date(parsedValue)
          this.leftDate = prevMonth(parsedValue)
          this.rightDate = new Date(parsedValue)
        }
      }
    },

    handleDateChange (event, type) {
      const value = event.target.value
      const parsedValue = parseDate(value, this.dateFormat)
      if (parsedValue) {
        if (type === 'min') {
          this.minDate = modifyDate(this.minDate, parsedValue.getFullYear(), parsedValue.getMonth(), parsedValue.getDate())
          if (this.minDate > this.maxDate) {
            this.maxDate = this.minDate
          }
        } else {
          this.maxDate = modifyDate(this.maxDate, parsedValue.getFullYear(), parsedValue.getMonth(), parsedValue.getDate())
          if (this.maxDate < this.minDate) {
            this.minDate = this.maxDate
          }
        }
      }
    },

    handleTimeChange (event, type) {
      const value = event.target.value
      const parsedValue = parseDate(value, this.timeFormat)
      if (parsedValue) {
        if (type === 'min') {
          this.minDate = modifyTime(this.minDate, parsedValue.getHours(), parsedValue.getMinutes(), parsedValue.getSeconds())
          if (this.minDate > this.maxDate) {
            this.maxDate = this.minDate
          }
          this.$refs.minTimePicker.value = this.minDate
          this.minTimePickerVisible = false
        } else {
          this.maxDate = modifyTime(this.maxDate, parsedValue.getHours(), parsedValue.getMinutes(), parsedValue.getSeconds())
          if (this.maxDate < this.minDate) {
            this.minDate = this.maxDate
          }
          this.$refs.maxTimePicker.value = this.minDate
          this.maxTimePickerVisible = false
        }
      }
    },

    handleRangePick (val, close = true) {
      const defaultTime = this.defaultTime || []
      const minDate = modifyWithTimeString(val.minDate, defaultTime[0])
      const maxDate = modifyWithTimeString(val.maxDate, defaultTime[1])

      if (this.maxDate === maxDate && this.minDate === minDate) {
        return
      }
      this.onPick && this.onPick(val)
      this.maxDate = maxDate
      this.minDate = minDate

      // workaround for https://github.com/ElemeFE/element/issues/7539, should remove this block when we don't have to care about Chromium 55 - 57
      setTimeout(() => {
        this.maxDate = maxDate
        this.minDate = minDate
      }, 10)
      if (!close || this.showTime) return
      this.handleConfirm()
    },

    handleShortcutClick (shortcut) {
      if (shortcut.onClick) {
        shortcut.onClick(this)
      }
    },

    handleMinTimePick (value, visible, first) {
      this.minDate = this.minDate || new Date()
      if (value) {
        this.minDate = modifyTime(this.minDate, value.getHours(), value.getMinutes(), value.getSeconds())
      }

      if (!first) {
        this.minTimePickerVisible = visible
      }

      if (!this.maxDate || (this.maxDate && this.maxDate.getTime() < this.minDate.getTime())) {
        this.maxDate = new Date(this.minDate)
      }
    },

    handleMinTimeClose () {
      this.minTimePickerVisible = false
    },

    handleMaxTimePick (value, visible, first) {
      if (this.maxDate && value) {
        this.maxDate = modifyTime(this.maxDate, value.getHours(), value.getMinutes(), value.getSeconds())
      }

      if (!first) {
        this.maxTimePickerVisible = visible
      }

      if (this.maxDate && this.minDate && this.minDate.getTime() > this.maxDate.getTime()) {
        this.minDate = new Date(this.maxDate)
      }
    },

    handleMaxTimeClose () {
      this.maxTimePickerVisible = false
    },

    // leftPrev*, rightNext* need to take care of `unlinkPanels`
    leftPrevYear () {
      this.leftDate = prevYear(this.leftDate)
      if (!this.unlinkPanels) {
        this.rightDate = nextMonth(this.leftDate)
      }
    },

    leftPrevMonth () {
      this.leftDate = prevMonth(this.leftDate)
      if (!this.unlinkPanels) {
        this.rightDate = nextMonth(this.leftDate)
      }
    },

    rightNextYear () {
      if (!this.unlinkPanels) {
        this.leftDate = nextYear(this.leftDate)
        this.rightDate = nextMonth(this.leftDate)
      } else {
        this.rightDate = nextYear(this.rightDate)
      }
    },

    rightNextMonth () {
      if (!this.unlinkPanels) {
        this.leftDate = nextMonth(this.leftDate)
        this.rightDate = nextMonth(this.leftDate)
      } else {
        this.rightDate = nextMonth(this.rightDate)
      }
    },

    // leftNext*, rightPrev* are called when `unlinkPanels` is true
    leftNextYear () {
      this.leftDate = nextYear(this.leftDate)
    },

    leftNextMonth () {
      this.leftDate = nextMonth(this.leftDate)
    },

    rightPrevYear () {
      this.rightDate = prevYear(this.rightDate)
    },

    rightPrevMonth () {
      this.rightDate = prevMonth(this.rightDate)
    },

    handleConfirm (visible = false) {
      if (this.isValidValue([this.minDate, this.maxDate])) {
        this.$emit('pick', [this.minDate, this.maxDate], visible)
      }
    },

    isValidValue (value) {
      return Array.isArray(value) &&
        value && value[0] && value[1] &&
        isDate(value[0]) && isDate(value[1]) &&
        value[0].getTime() <= value[1].getTime() && (
        typeof this.disabledDate === 'function'
          ? !this.disabledDate(value[0]) && !this.disabledDate(value[1])
          : true
      )
    },

    resetView () {
      // NOTE: this is a hack to reset {min, max}Date on picker open.
      // TODO: correct way of doing so is to refactor {min, max}Date to be dependent on value and internal selection state
      //       an alternative would be resetView whenever picker becomes visible, should also investigate date-panel's resetView
      this.minDate = this.value && isDate(this.value[0]) ? new Date(this.value[0]) : null
      this.maxDate = this.value && isDate(this.value[0]) ? new Date(this.value[1]) : null
    }
  }
}
</script>
